import Data.List
import Control.Monad
import Data.Maybe

type Movie = (Title,Regisseur,MainActors,ReleaseDate,Genre,SalesPrice)
type Title = String
type Regisseur = String
type Actor = String
type MainActors = [Actor]
type ReleaseDate = Int
data Genre = Thriller | Fantasy | ScienceFiction | Comedy deriving (Eq,Ord,Show)
type SalesPrice = Int
type Database = [Movie]

rm_dup :: Database -> Database
rm_dup = nubBy f
  where
    f (t,r,a,d,g,p) (t2,r2,a2,d2,g2,p2) =
      t == t2 &&
      r == r2 &&
      (sort a) == (sort a2) &&
      d == d2 &&
      g == g2 &&
      p == p2

-- Liefere alle Regisseure zusammen mit den Filmtiteln und dem Erscheinungsjahr:
get_rtd :: Database -> [(Regisseur,Title,ReleaseDate)]
get_rtd = map f
  where
    f (t,r,_,d,_,_) = (r,t,d)

--Liefere alle Regisseure zusammen mit den Filmtiteln und dem Genre,
--die zugleich (einer der) Hauptdarsteller in diesem Film waren:
get_rtg :: Database -> [(Regisseur,Title,Genre)]
get_rtg = map f . filter g
  where
    g (_,r,a,_,_,_) = elem r a

    f (t,r,_,_,g,_) = (r,t,g)

--Liefere die Titel aller Filme zusammen mit den Listen ihrer Hauptdarsteller,
--die im Jahr n erschienen sind:
get_tad :: Database -> ReleaseDate -> [(Title,MainActors,ReleaseDate)]
get_tad db n = map f . filter g $ db
  where
    g (_,_,_,d,_,_) = n == d

    f (t,_,a,d,_,_) = (t,a,d)

--Liefere die Liste aller Filme zusammen mit dem Erscheinungsjahr, in denen Schauspieler s
--einer der Hauptdarsteller war:
get_atr :: Database -> Actor -> [(Actor,Title,ReleaseDate)]
get_atr db s = map f . filter g $ db
  where
    g (_,_,a,_,_,_) = elem s a

    f (t,_,a,d,_,_) = (s,t,d)


--Verandere die Preise aller Filme des Genres g, in denen Regisseur r Regie gefuhrt hat, um x.
--Fur positives x fuhrt dies zu einer Erhohung, fur negatives x zu einer Erniedrigung. Ist der
--sich so ergebende neue Preis kleiner oder gleich 0, wird er auf 1 gesetzt:
upd_dbgri :: Database -> Genre -> Regisseur -> Int -> Database
upd_dbgri db g' r' x = map f . filter g $ db
  where
    g (_,r,_,_,g,_) = (r,g) == (r',g')

    f (t,r,a,d,g,p) = (t,r,a,d,g,p')
      where
        p' | p - x <= 0 = 1
           | otherwise = p - x


--Losche alle Filme, in denen Schauspieler s einer der Hauptdarsteller war und die im Jahr j
--oder spater erschienen sind:
upd_dbad :: Database -> Actor -> ReleaseDate -> Database
upd_dbad db s j = filter f db
  where
    f (t,r,a,d,g,p) = (not $ elem s a) || (d < j)


--Liefere alle Filme, die im Jahr j oder fruher erschienen sind und in denen Schauspieler s nicht
--unter den Hauptdarstellern war:
get_dbda :: Database -> ReleaseDate -> Actor -> Database
get_dbda db j s = filter f db
  where
    f (t,r,a,d,g,p) = d <= j && (not $ elem s a)

--Sortiere die Datenbank absteigend nach Erscheinungsjahr, d.h. neuere Filme vor alteren Fil- ¨
--men. Sind in einem Jahr mehrere Filme erschienen, ist die Reihenfolge unerheblich, in der diese
--Filme nach der Sortierung aufgefuhrt sind: ¨
sort_dbj :: Database -> Database
sort_dbj db = reverse $ sortBy f db
  where
    f (_,_,_,d1,_,_) (_,_,_,d2,_,_) = compare d1 d2


--Sortiere die Datenbank aufsteigend nach Genres und innerhalb der Genres aufsteigend nach
--Regisseuren; hat ein Regisseur innerhalb eines Genres mehrfach Regie gefuhrt, ist die Reihen- ¨
--folge unerheblich, in der diese Filme nach der Sortierung aufgefuhrt sind: ¨
sort_dbgr :: Database -> Database
sort_dbgr db = sortBy f db
  where
    f (_,r1,_,_,g1,_) (_,r2,_,_,g2,_) = compare (g1,r1) (g2,r2)


------------------------------------------------------------------------------------
------------------------------------------------------------------------------------
-- this implementation is dumb and unbelievably inefficient. But hey, whatever man...

type ListOfValues = [Integer]
type TargetValue = Integer
type Game = (ListOfValues,TargetValue)
data Operators = Plus | Times | Minus | Div deriving (Eq,Ord,Show)
type Solution = [Operators]

evaluate :: ListOfValues -> Solution -> Maybe Integer
evaluate [v] [] = Just v
evaluate (v1:v2:vs) (Plus:ops) = evaluate ((v1 + v2):vs) ops
evaluate (v1:v2:vs) (Times:ops) = evaluate ((v1 * v2):vs) ops
evaluate (v1:v2:vs) (Minus:ops) = evaluate ((v1 - v2):vs) ops
evaluate (v1:v2:vs) (Div:ops)
  | v2 == 0 = Nothing
  | otherwise = evaluate ((v1 `div` v2):vs) ops

isSolution :: ListOfValues -> TargetValue -> Solution -> Bool
isSolution vs t sol =
  case (evaluate vs sol) of
    Just e -> e == t
    _ -> False

solve :: Game -> Solution
solve ([],t) = []
solve (vs,t) = getOneSolution $ replicateM (length vs - 1) [Plus,Times,Minus,Div]
  where
    getOneSolution [] = []
    getOneSolution (s:ss)
      | isSolution vs t s = s
      | otherwise = getOneSolution ss
